/** \file lp_app.c
 * \brief Loader for the gladefile containing the LogicParser GTK+ gui.
 * \author Marcelo Lira dos Santos
 * \date 2006-2007
 *
 * This is free software; you can redistribute it and/or modify it under the
 * terms of the GNU General Public License, see the file LICENSE.
 */
#include "lp_app.h"

/** \brief Initializes the LogicParser application.
 * Loads the ui from the glade file, create the data models and connect the
 * callbacks.
 * \param gladefile The path of the glade ui file.
 * \return A LpApp structure containting the loaded gui.
 */
LpApp *
lp_app_init (gchar *gladefile)
{
	LpApp *lpApp = NULL;

	lpApp = g_new0 (LpApp, 1);
	lpApp->widgetTree = glade_xml_new (gladefile, NULL, NULL);
	
	// Load Widgets
	lpApp->lpWindow = glade_xml_get_widget (lpApp->widgetTree, "LPWindow");
	gtk_window_set_icon_from_file (GTK_WINDOW(lpApp->lpWindow), APP_ICON, NULL);
	
	lpApp->lpAboutDialog = glade_xml_get_widget (lpApp->widgetTree, "LPAbout");
	lpApp->exprEntry = glade_xml_get_widget (lpApp->widgetTree, "exprEntry");
	lpApp->parsedTree = glade_xml_get_widget (lpApp->widgetTree, "parsedTree");
	lpApp->propTree = glade_xml_get_widget (lpApp->widgetTree, "propTree");
	lpApp->graphText = glade_xml_get_widget (lpApp->widgetTree, "graphText");
	lpApp->graphImage = glade_xml_get_widget (lpApp->widgetTree, "graphImage");
	lpApp->dotToolbar = glade_xml_get_widget (lpApp->widgetTree, "dot_toolbar");
	lpApp->imageToolbar = glade_xml_get_widget (lpApp->widgetTree, "image_toolbar");
	
	lpApp->viewport = glade_xml_get_widget (lpApp->widgetTree, "viewport");
	
	gtk_window_resize (GTK_WINDOW(lpApp->lpWindow), 600, 500);
	gtk_window_set_transient_for (GTK_WINDOW(lpApp->lpAboutDialog),
											GTK_WINDOW(lpApp->lpWindow));
	g_signal_connect(G_OBJECT(lpApp->lpAboutDialog), "delete-event",
							G_CALLBACK(gtk_widget_hide_on_delete), NULL);
	g_signal_connect(G_OBJECT(lpApp->lpAboutDialog), "response",
							G_CALLBACK(gtk_widget_hide), NULL);

	// Create data model
	lp_app_init_parsed_tree (lpApp);
	lp_app_init_prop_list (lpApp);
	lp_app_init_text (lpApp);
	
	// Connect callbacks
	glade_xml_signal_connect (lpApp->widgetTree, "quit_cb",
										GTK_SIGNAL_FUNC (quit_cb));
	glade_xml_signal_connect_data (lpApp->widgetTree, "about_cb",
											GTK_SIGNAL_FUNC (about_cb),
											LPAPP_TO_GPOINTER(lpApp));
	glade_xml_signal_connect_data (lpApp->widgetTree, "parse_cb",
											GTK_SIGNAL_FUNC (parse_cb),
											LPAPP_TO_GPOINTER(lpApp));
	glade_xml_signal_connect_data (lpApp->widgetTree, "save_image_cb",
											GTK_SIGNAL_FUNC (save_image_cb),
											LPAPP_TO_GPOINTER(lpApp));
	glade_xml_signal_connect_data (lpApp->widgetTree, "save_dot_cb",
											GTK_SIGNAL_FUNC (save_dot_cb),
											LPAPP_TO_GPOINTER(lpApp));
	glade_xml_signal_connect_data (lpApp->widgetTree, "zoom_in_cb",
											GTK_SIGNAL_FUNC (zoom_in_cb),
											LPAPP_TO_GPOINTER(lpApp));
	glade_xml_signal_connect_data (lpApp->widgetTree, "zoom_out_cb",
											GTK_SIGNAL_FUNC (zoom_out_cb),
											LPAPP_TO_GPOINTER(lpApp));
	glade_xml_signal_connect_data (lpApp->widgetTree, "normal_view_cb",
											GTK_SIGNAL_FUNC (normal_view_cb),
											LPAPP_TO_GPOINTER(lpApp));
	glade_xml_signal_connect_data (lpApp->widgetTree, "fit_view_cb",
											GTK_SIGNAL_FUNC (fit_view_cb),
											LPAPP_TO_GPOINTER(lpApp));

	return lpApp;
}

/** \brief Show the application main window and enter the main loop.
 * \param lpApp A LpApp structure containting the gui and its components.
 */
void
lp_app_run (LpApp *lpApp)
{
	gtk_widget_show_all(lpApp->lpWindow);
	gtk_main ();
}

/** \brief Initializes the parsed tree TreeView and its data model.
 * \param lpApp A LpApp structure containting the gui and its components.
 */
void
lp_app_init_parsed_tree (LpApp *lpApp)
{
	GtkTreeViewColumn *column;
	GtkCellRenderer *renderer;
	
	// Parsed proposition nodes, Text value, Type of node, Id of prop node
	lpApp->parsedTreeModel = gtk_tree_store_new (4, G_TYPE_STRING, G_TYPE_STRING,
																G_TYPE_INT, G_TYPE_INT);

	renderer = gtk_cell_renderer_text_new ();
	column = gtk_tree_view_column_new_with_attributes (
									"Parsed Tree",
									renderer,
									"text", 0,
									NULL);
	gtk_tree_view_append_column (GTK_TREE_VIEW(lpApp->parsedTree), column);
	gtk_tree_view_column_set_expand (column, TRUE);
	
	renderer = gtk_cell_renderer_text_new ();
	column = gtk_tree_view_column_new_with_attributes (
									"Value",
									renderer,
									"text", 1,
									NULL);
	gtk_tree_view_append_column (GTK_TREE_VIEW(lpApp->parsedTree), column);
	gtk_tree_view_column_set_expand (column, FALSE);
	
	gtk_tree_view_set_model(GTK_TREE_VIEW(lpApp->parsedTree),
									GTK_TREE_MODEL(lpApp->parsedTreeModel));

}

/** \brief Initializes the value TreeView and its data model.
 * \param lpApp A LpApp structure containting the gui and its components.
 */
void
lp_app_init_prop_list (LpApp *lpApp)
{
	GtkTreeViewColumn *column;
	GtkCellRenderer *renderer;
	GtkListStore *comboList;
	GtkTreeIter iter;
	
	// Text of prop node, Text value, Id of prop node
	lpApp->propListModel = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING,
																G_TYPE_INT);
																
	renderer = gtk_cell_renderer_text_new ();
	column = gtk_tree_view_column_new_with_attributes (
									"Proposition",
									renderer,
									"text", 0,
									NULL);
	gtk_tree_view_append_column (GTK_TREE_VIEW(lpApp->propTree), column);
	
	renderer = gtk_cell_renderer_combo_new ();
	column = gtk_tree_view_column_new_with_attributes (
									"Value",
									renderer,
									"text", 1,
									NULL);
	g_object_set(G_OBJECT(renderer), "editable", TRUE, NULL);

	comboList = gtk_list_store_new (1, G_TYPE_STRING);
	gtk_list_store_append (comboList, &iter);
	gtk_list_store_set (comboList, &iter, 0, "False", -1);
	gtk_list_store_append (comboList, &iter);
	gtk_list_store_set (comboList, &iter, 0, "True", -1);
	g_object_set(G_OBJECT(renderer), "model", comboList, NULL);
	g_object_set(G_OBJECT(renderer), "has-entry", FALSE, NULL);
	g_object_set(G_OBJECT(renderer), "editable", TRUE, NULL);
	g_object_set(G_OBJECT(renderer), "text-column", 0, NULL);
	g_signal_connect(renderer, "edited", G_CALLBACK(change_prop_value_cb), lpApp);

	gtk_tree_view_append_column (GTK_TREE_VIEW(lpApp->propTree), column);

	gtk_tree_view_set_model(GTK_TREE_VIEW(lpApp->propTree),
									GTK_TREE_MODEL(lpApp->propListModel));
}

/** \brief Initializes the TextView and its data model.
 * \param lpApp A LpApp structure containting the gui and its components.
 */
void
lp_app_init_text (LpApp *lpApp)
{
	PangoFontDescription *font;

	font = pango_font_description_from_string ("Mono 12");
	gtk_widget_modify_font (lpApp->graphText, font);
	pango_font_description_free (font);

	lpApp->graphTextBuffer = gtk_text_buffer_new (NULL);
	gtk_text_view_set_buffer (GTK_TEXT_VIEW(lpApp->graphText),
										lpApp->graphTextBuffer);
}

/** \brief Updates the parsed and prop TreeViews with the parsed tree and
 * \brief proposition values, or an error message.
 * \param lpApp A LpApp structure containting the gui and its components.
 * \param root A LpNode pointing to the root of the parsed expression tree.
 */
void
lp_app_update_trees (LpApp *lpApp, LpNode *root)
{
	//GSList *list;

	lpApp->propList = NULL;
	gtk_list_store_clear (lpApp->propListModel);
	lp_app_add_prop_node (lpApp, root);

	gtk_tree_store_clear (lpApp->parsedTreeModel);
	lp_app_add_parsed_node (lpApp->parsedTreeModel, NULL, root);
	gtk_tree_view_expand_all (GTK_TREE_VIEW(lpApp->parsedTree));

	lp_evaluate (lpApp->parsedTreeModel, NULL);
	
	/*list = g_slist_nth (lpApp->propList, 0);
	while ((list = g_slist_next(list)) != NULL)
		g_print("%d, ", list->data);
	g_print("\n");*/
}

/** \brief Append a note of the parsed tree to the TreeView.
 * \param parsedTreeModel Data model for the TreeView.
 * \param parent GtkTreeModel iterator representing the parent row.
 * \param node A LpNode pointing to the root of the parsed expression tree.
 */
void
lp_app_add_parsed_node (GtkTreeStore *parsedTreeModel, 
								GtkTreeIter *parent,
								LpNode *node)
{
	gchar *node_text;
	GtkTreeIter iter;

	if (node != NULL) {
		if (node->lpToken->nodeType == C_ID)
			node_text = g_strdup_printf("P%d", node->lpToken->nodeId);
		else if (node->lpToken->nodeType == C_IF)
			node_text = g_strdup_printf("->");
		else
			node_text = g_strdup_printf("%c", node->lpToken->nodeType);

		gtk_tree_store_append (parsedTreeModel, &iter, parent);		
		gtk_tree_store_set (parsedTreeModel, &iter,
							0, node_text,
							1, FALSE_TEXT,
							2, node->lpToken->nodeType,
							3, node->lpToken->nodeId,
							-1);

		lp_app_add_parsed_node (parsedTreeModel, &iter, node->left);
		lp_app_add_parsed_node (parsedTreeModel, &iter, node->right);
	}
}

/** \brief Append a note of the parsed tree to the TreeView.
 * \param parsedTreeModel Data model for the TreeView.
 * \param parent GtkTreeModel iterator representing the parent row.
 * \param node A LpNode pointing to the root of the parsed expression tree.
 */
void
lp_app_add_prop_node (LpApp *lpApp, LpNode *node)
{
	gchar *node_text;
	GtkTreeIter iter;
	
	if (node != NULL) {
		if (node->lpToken->nodeType == C_ID &&
				g_slist_find (lpApp->propList, node->lpToken->nodeId) == NULL) {
			lpApp->propList = g_slist_append (lpApp->propList,
														node->lpToken->nodeId);
			node_text = g_strdup_printf("P%d", node->lpToken->nodeId);
		
			gtk_list_store_append (lpApp->propListModel, &iter);
			gtk_list_store_set (lpApp->propListModel, &iter,
										0, node_text,
										1, FALSE_TEXT,
										2, node->lpToken->nodeId,
										-1);
		}

		lp_app_add_prop_node (lpApp, node->left);
		lp_app_add_prop_node (lpApp, node->right);
	}
}

/** \brief Append a note of the parsed tree to the TreeView.
 * \param lpApp A LpApp structure containting the gui and its components.
 * \param root A LpNode pointing to the root of the parsed expression tree.
 */
void
lp_app_update_graph (LpApp *lpApp, LpNode *root)
{
	gchar *graph_img_file = g_strconcat (g_get_tmp_dir (), "/graph.png", NULL);
	gchar *dotgraph = lp_graph_generate (root);
	
	gtk_text_buffer_set_text (lpApp->graphTextBuffer, dotgraph, -1);
	
	if (lp_graph_save_as_image (dotgraph, graph_img_file)) {
		lpApp->graphPixbuf = gdk_pixbuf_new_from_file(graph_img_file, NULL);
		lpApp->zoom = 1.0;
		lpApp->pixbuf_height = gdk_pixbuf_get_height(lpApp->graphPixbuf);
		lpApp->pixbuf_width = gdk_pixbuf_get_width(lpApp->graphPixbuf);
		gtk_image_set_from_pixbuf(GTK_IMAGE(lpApp->graphImage),
											lpApp->graphPixbuf);
	}

	g_free(graph_img_file);
}

/** \brief Change the value of all instances of the indicated proposition 
 * \brief in the parsed tree.
 * \param parsedTreeModel parsedTreeModel
 * \param nodeId nodeId
 */
void
lp_change_prop_value (GtkTreeStore *parsedTreeModel, GtkTreeIter *parent,
								gint nodeId, gchar *value)
{
	GtkTreeIter children;
	gint old_nodeId;
	
	if (gtk_tree_model_iter_children (GTK_TREE_MODEL (parsedTreeModel),
												&children, parent)) {
		lp_change_prop_value (parsedTreeModel, &children, nodeId, value);
		if (gtk_tree_model_iter_next (GTK_TREE_MODEL (parsedTreeModel),
												&children)) {
			lp_change_prop_value (parsedTreeModel, &children, nodeId, value);
		}
	} else {
		gtk_tree_model_get (GTK_TREE_MODEL (parsedTreeModel), parent,
							3, &old_nodeId,
							-1);
		if (nodeId == old_nodeId)
			gtk_tree_store_set (parsedTreeModel, parent, 1, value, -1);
	}
}

gchar *
save_file (GtkWindow *parent_window, gchar *type)
{
	GtkWidget *dialog;
	GtkFileFilter *filter;
	gchar *filename = NULL;
	gchar *pattern = g_strconcat("*.", type, NULL);
	gchar *name_suggest = g_strconcat("unnamed.", type, NULL);
	
	dialog = gtk_file_chooser_dialog_new ("Save File",
														parent_window,
														GTK_FILE_CHOOSER_ACTION_SAVE,
														GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
														GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
														NULL);
	gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog),
																	TRUE);

	filter = gtk_file_filter_new();
	gtk_file_filter_add_pattern (filter, pattern);
	gtk_file_chooser_set_filter(GTK_FILE_CHOOSER (dialog), filter);
	gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER (dialog),
													g_get_home_dir());
	gtk_file_chooser_set_filter(GTK_FILE_CHOOSER (dialog), filter);
	gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), name_suggest);

	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
		filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));

	gtk_widget_destroy (dialog);

	return filename;
}

/** \brief Draw the graph image.
 * \param lpApp A pointer to an LpApp structure.
 */
void
lp_app_draw_graph_image (LpApp *lpApp)
{
	GdkPixbuf *pixbuf;
	
	//pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(lpApp->graphImage));
	//g_object_unref (pixbuf);

	pixbuf = gdk_pixbuf_scale_simple(lpApp->graphPixbuf,
												lpApp->pixbuf_width * lpApp->zoom,
												lpApp->pixbuf_height * lpApp->zoom,
												GDK_INTERP_BILINEAR);

	gtk_image_set_from_pixbuf(GTK_IMAGE(lpApp->graphImage), pixbuf);
}


// Callbacks ------------------------------------------------------------------

/** \brief Quits the application.
 * \param widget The widget that generated the signal.
 * \param e A GdkEventKey.
 */
void
quit_cb (GtkWidget *widget, GdkEventKey *e)
{
	gtk_main_quit();
}

/** \brief Show the about dialog.
 * \param widget The widget that generareturns.ted the signal.
 * \param data A gpointer to an LpApp structure.
 */
void
about_cb (GtkWidget *widget, gpointer data)
{
	GtkWidget *about;
	LpApp *lpApp = GPOINTER_TO_LPAPP(data);
	about = glade_xml_get_widget (lpApp->widgetTree, "LPAbout");
	gtk_dialog_run (GTK_DIALOG (about));
}

/** \brief Parse the expression in the exprEntry text entry.
 * \param widget The widget that generated the signal.
 * \param data A gpointer to an LpApp structure.
 */
void
parse_cb (GtkWidget *widget, gpointer data)
{
	gchar *expr;
	LpNode *root;
	LpApp *lpApp = GPOINTER_TO_LPAPP(data);
	
	gtk_widget_set_sensitive(lpApp->dotToolbar, TRUE);
	gtk_widget_set_sensitive(lpApp->imageToolbar, TRUE);

	expr = g_ascii_strup (g_strchomp (gtk_entry_get_text
													(GTK_ENTRY(lpApp->exprEntry))), -1);

	root = lp_parse_expression (expr);
	lp_app_update_trees (lpApp, root);
	lp_app_update_graph (lpApp, root);

}

/** \brief Save the image generated by GraphViz for the parsed tree.
 * \param widget The widget that generated the signal.
 * \param data A gpointer to an LpApp structure.
 */
void
save_image_cb (GtkWidget *widget, gpointer data)
{
	LpApp *lpApp = GPOINTER_TO_LPAPP(data);
	gchar *filename;

	filename = save_file(GTK_WINDOW(lpApp->lpWindow), "png");

	if (filename != NULL) {
		gdk_pixbuf_savev (lpApp->graphPixbuf, filename, "png", NULL, NULL, NULL);
		g_free(filename);
	}
}

/** \brief Save the GraphViz's dot file for the parsed tree.
 * \param widget The widget that generated the signal.
 * \param data A gpointer to an LpApp structure.
 */
void
save_dot_cb (GtkWidget *widget, gpointer data)
{
	LpApp *lpApp = GPOINTER_TO_LPAPP (data);
	FILE *dot_file;
	gchar *filename;
	gchar *graph_text;
	GtkTextIter start;
	GtkTextIter end;

	filename = save_file (GTK_WINDOW(lpApp->lpWindow), "dot");

	if (filename != NULL) {
		gtk_text_buffer_get_bounds (lpApp->graphTextBuffer, &start, &end);
		graph_text = gtk_text_buffer_get_text (lpApp->graphTextBuffer,
															&start, &end, FALSE);

		dot_file = g_fopen (filename, "w");
		g_fprintf (dot_file, "%s", graph_text);
		close (dot_file);

		g_free (filename);
	}
}

/** \brief Zoom in the graph of the parsed tree.
 * \param widget The widget that generated the signal.
 * \param data A gpointer to an LpApp structure.
 */
void
zoom_in_cb (GtkWidget *widget, gpointer data)
{
	LpApp *lpApp = GPOINTER_TO_LPAPP(data);
	lpApp->zoom = lpApp->zoom * 2;
	lp_app_draw_graph_image (lpApp);
}

/** \brief Zoom out the graph of the parsed tree.
 * \param widget The widget that generated the signal.
 * \param data A gpointer to an LpApp structure.
 */
void
zoom_out_cb (GtkWidget *widget, gpointer data)
{
	LpApp *lpApp = GPOINTER_TO_LPAPP(data);
	lpApp->zoom = lpApp->zoom / 2;
	lp_app_draw_graph_image (lpApp);
}

/** \brief Show the normal zoom of the graph of the parsed tree.
 * \param widget The widget that generated the signal.
 * \param data A gpointer to an LpApp structure.
 */
void
normal_view_cb (GtkWidget *widget, gpointer data)
{
	LpApp *lpApp = GPOINTER_TO_LPAPP(data);
	lpApp->zoom = 1.0;
	lp_app_draw_graph_image (lpApp);
}

/** \brief Change the zoom of the graph of the parsed tree to fit the window.
 * \param widget The widget that generated the signal.
 * \param data A gpointer to an LpApp structure.
 */
void
fit_view_cb (GtkWidget *widget, gpointer data)
{
	LpApp *lpApp = GPOINTER_TO_LPAPP(data);
	gint vp_width;
	gint vp_height;
	gdouble hscale;
	gdouble vscale;
				
	vp_width = GTK_WIDGET(lpApp->viewport)->allocation.width - 4;
	vp_height = GTK_WIDGET(lpApp->viewport)->allocation.height - 4;

	hscale = (gdouble) vp_width / lpApp->pixbuf_width;
	vscale = (gdouble) vp_height / lpApp->pixbuf_height;
	
	if (hscale < vscale)
		lpApp->zoom = hscale;
	else
		lpApp->zoom = vscale;

	lp_app_draw_graph_image (lpApp);
}

/** \brief Change the value of a proposition.
 * \param widget The widget that generated the signal.
 * \param cell cell
 * \param path_string path_string
 * \param new_text new_text
 * \param data A gpointer to an LpApp structure.
 */
void
change_prop_value_cb (GtkCellRendererCombo *cell, gchar *path_string,
								gchar *new_text, gpointer data)
{
	GtkTreeIter iter;
	gint nodeId;
	LpApp *lpApp = GPOINTER_TO_LPAPP(data);
	
	if (gtk_tree_model_get_iter_from_string (lpApp->propListModel, &iter,
															path_string)) {
		gtk_list_store_set (lpApp->propListModel, &iter, 1, new_text, -1);
		gtk_tree_model_get (GTK_TREE_MODEL (lpApp->propListModel), &iter,
							2, &nodeId,
							-1);
		lp_change_prop_value (lpApp->parsedTreeModel, NULL, nodeId, new_text);
		lp_evaluate (lpApp->parsedTreeModel, NULL);
	}
}
